package com.hub.frame.interceptor;

import com.hub.frame.exception.AdminPermissionDeniedException;
import com.hub.frame.exception.FileException;
import com.hub.frame.exception.PermissionDeniedException;
import com.hub.frame.exception.TooFrequentException;
import com.hub.utils.ApiCodeEnum;
import com.hub.utils.ApiResultObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.util.List;

/**
 * @author hub
 * @version 1.0
 * @date 2020/5/13 11:16
 * @description:
 */

@Slf4j
@ControllerAdvice
public class GlobalDefaultExceptionHandler {

    /**
     * NoHandlerFoundException 404 异常处理
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiResultObject handlerNoHandlerFoundException(NoHandlerFoundException exception) {
        outPutErrorWarn(NoHandlerFoundException.class, ApiCodeEnum.NOT_FOUND, exception);
        return ApiResultObject.failed(ApiCodeEnum.NOT_FOUND);
    }

    /**
     * HttpRequestMethodNotSupportedException 405 异常处理
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ApiResultObject handlerHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException exception) {
        outPutErrorWarn(HttpRequestMethodNotSupportedException.class,
                ApiCodeEnum.METHOD_NOT_ALLOWED, exception);
        return ApiResultObject.failed(ApiCodeEnum.METHOD_NOT_ALLOWED);
    }

    /**
     * HttpMediaTypeNotSupportedException 415 异常处理
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ApiResultObject handlerHttpMediaTypeNotSupportedException(
            HttpMediaTypeNotSupportedException exception) {
        outPutErrorWarn(HttpMediaTypeNotSupportedException.class,
                ApiCodeEnum.UNSUPPORTED_MEDIA_TYPE, exception);
        return ApiResultObject.failed(ApiCodeEnum.UNSUPPORTED_MEDIA_TYPE);
    }

    /**
     * Exception 类捕获 500 异常处理
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ApiResultObject handlerException(Exception e) {
        return ifDepthExceptionType(e);
    }

    /**
     * 二次深度检查错误类型
     */
    private ApiResultObject ifDepthExceptionType(Throwable throwable) {
        Throwable cause = throwable.getCause();
//        if (cause instanceof ClientException) {
//            return handlerClientException((ClientException) cause);
//        }
//        if (cause instanceof FeignException) {
//            return handlerFeignException((FeignException) cause);
//        }
        outPutError(Exception.class, ApiCodeEnum.EXCEPTION, throwable);
        return ApiResultObject.failed(ApiCodeEnum.EXCEPTION);
    }


    /**
     * ClientException 类捕获
     */
    @ExceptionHandler(value = FileException.class)
    public ApiResultObject handlerClientException(FileException e) {
        outPutError(FileException.class, ApiCodeEnum.PARAM_ERROR, e);
        String msg = String.format("%s : 错误详情( %s )", ApiCodeEnum.PARAM_ERROR.getDesc(),
                e.getMessage());
        return ApiResultObject.failed(ApiCodeEnum.PARAM_ERROR.getCode(), msg);
    }


//    /**
//     * FeignException 类捕获
//     */
//    @ExceptionHandler(value = FeignException.class)
//    public Result handlerFeignException(FeignException e) {
//        outPutError(FeignException.class, CommonErrorCode.RPC_ERROR, e);
//        return Result.ofFail(CommonErrorCode.RPC_ERROR);
//    }
//
//    /**
//     * ClientException 类捕获
//     */
//    @ExceptionHandler(value = ClientException.class)
//    public Result handlerClientException(ClientException e) {
//        outPutError(ClientException.class, CommonErrorCode.RPC_ERROR, e);
//        return Result.ofFail(CommonErrorCode.RPC_ERROR);
//    }

//    /**
//     * BusinessException 类捕获
//     */
//    @ExceptionHandler(value = BusinessException.class)
//    public Result handlerBusinessException(BusinessException e) {
//        outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
//        return Result.ofFail(e.getCode(), e.getMessage());
//    }

    /**
     * HttpMessageNotReadableException 参数错误异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ApiResultObject handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        outPutError(HttpMessageNotReadableException.class, ApiCodeEnum.PARAM_ERROR, e);
        String msg = String.format("%s : 错误详情( %s )", ApiCodeEnum.PARAM_ERROR.getDesc(),
                e.getRootCause().getMessage());
        return ApiResultObject.failed(ApiCodeEnum.PARAM_ERROR.getCode(), msg);
    }

    /**
     * BindException 参数错误异常
     */
    @ExceptionHandler(BindException.class)
    public ApiResultObject handleMethodArgumentNotValidException(BindException e) {
        outPutError(BindException.class, ApiCodeEnum.PARAM_ERROR, e);
        BindingResult bindingResult = e.getBindingResult();
        return getBindResultDTO(bindingResult);
    }

    /**
     * NoHandlerFoundException 429 连续发送相同请求
     */
    @ExceptionHandler(value = TooFrequentException.class)
    @ResponseBody
    public ApiResultObject handlerTooFrequentException(TooFrequentException exception) {
        outPutErrorWarn(TooFrequentException.class, ApiCodeEnum.TRAFFIC_LIMITING, exception);
        return ApiResultObject.failed(ApiCodeEnum.TRAFFIC_LIMITING);
    }

    /**
     * AdminPermissionDeniedException 100 验证失败
     */
    @ExceptionHandler(value = AdminPermissionDeniedException.class)
    @ResponseBody
    public ApiResultObject handlerAdminPermissionDeniedException(AdminPermissionDeniedException exception) {
        outPutErrorWarn(AdminPermissionDeniedException.class, ApiCodeEnum.AuthFail, exception);
        return ApiResultObject.failed(ApiCodeEnum.AuthFail);
    }

    /**
     * PermissionDeniedException 403 权限不足
     */
    @ExceptionHandler(value = PermissionDeniedException.class)
    @ResponseBody
    public ApiResultObject handlerPermissionDeniedException(PermissionDeniedException exception) {
        outPutErrorWarn(PermissionDeniedException.class, ApiCodeEnum.FORBIDDEN, exception);
        return ApiResultObject.failed(ApiCodeEnum.FORBIDDEN);
    }

    /**
     * UnauthorizedException 401 无权限
     */
    @ExceptionHandler(value = UnauthorizedException.class)
    @ResponseBody
    public ApiResultObject handlerUnauthorizedException(UnauthorizedException exception) {
        outPutErrorWarn(UnauthorizedException.class, ApiCodeEnum.UNAUTHORIZED, exception);
        return ApiResultObject.failed(ApiCodeEnum.UNAUTHORIZED);
    }


    private ApiResultObject getBindResultDTO(BindingResult bindingResult) {
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (log.isDebugEnabled()) {
            for (FieldError error : fieldErrors) {
                log.error("{} -> {}", error.getDefaultMessage(), error.getDefaultMessage());
            }
        }

        if (fieldErrors.isEmpty()) {
            log.error("validExceptionHandler error fieldErrors is empty");
            ApiResultObject.failed(ApiCodeEnum.BUSINESS_ERROR.getCode(), "");
        }

        return ApiResultObject
                .failed(ApiCodeEnum.PARAM_ERROR.getCode(), fieldErrors.get(0).getDefaultMessage());
    }

    public void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
                throwable);
    }

    public void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
    }

}
